package dbexp.utils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import dbexp.model.db.DbUser;

/**
 * Klasa służąca do ładowania właściwości applikacji z pliku explorer.properties
 * 
 * @author Michał Skrzyński
 * @email michal_skrzynski@o2.pl
 */
public class DbProperties {
	/**
	 * zmienna reprezentująca obiekt czytający właściwości
	 */
	private Properties proper = null;
	/**
	 * statyczna wartości logiczna true
	 */
	public final static String TRUE = "true";
	/**
	 * statyczna wartości logiczna false
	 */
	public final static String FALSE = "false";
	/**
	 * Zmienna reprezentująca nazwę pliku właściwości
	 */
	private static String properFile = System.getProperty("properFile", "explorer.properties");

	/**
	 * Domyślna nazwa typu bazy
	 */
	private final static String DEFAULT_TYPE = "Oracle";

	/**
	 * Domyślna nazwa hosta
	 */
	private final static String DEFAULT_HOST = "localhost";
	/**
	 * Domyślny port 1521:Oracle,5432:Postgres
	 */
	private final static String DEFAULT_PORT = "1521";
	/**
	 * Domyślna nazwa sterownika org.postgresql.Driver
	 * oracle.jdbc.driver.OracleDriver
	 */
	private final static String DEFAULT_DRIVER_NAME = "oracle.jdbc.driver.OracleDriver";
	/**
	 * Domyślna nazwa url np. jdbc:oracle:thin:@localhost:1521:gwt
	 * dbc:postgresql://localhost:5432/centrala
	 */
	private final static String DEFAULT_URL = "jdbc:oracle:thin:@localhost:1521:orcl";

	/**
	 * Czy po wykonaniu zapytania ma być automatycznie zamykane połączenie z
	 * bazą - default false
	 */
	private final static boolean AUTO_CLOSE = false;
	/**
	 * Zmienna instancji singelton
	 */
	private static DbProperties instance = new DbProperties();

	/**
	 * zmienna określająca czy właściwości zostały załadowane z pliku
	 */
	private boolean loaded = false;

	private boolean showAllSchemas = false;

	private String checkPrivs = "always";

	/**
	 * Konstruktor singelton
	 */
	private DbProperties() {

	}

	/**
	 * @return statyczna instancja klasy właściwości
	 */
	public static synchronized DbProperties getInstance() {
		if (!instance.isLoaded()) {
			instance.loadProperties();
		}
		return instance;
	}

	/**
	 * Podczas braku załadowania pliku właściwości zwraca <code>null</code>
	 * 
	 * @param name
	 *            nazwa właściwości
	 * @return wartość właściwości
	 */
	public String getProperty(String name) {
		if (proper == null) {
			return null;
		}
		String value = proper.getProperty(name);
		return value;
	}

	/**
	 * @param name
	 *            nazwa właściwości
	 * @param defaultValue
	 *            wartość domyślna w przypadku braku wartości lub jeśli jest nią
	 *            pusty ciąg znaków
	 * @return wartość właściwości
	 */
	public String getProperty(String name, String defaultValue) {
		String value = getProperty(name);
		if (value == null || value.length() == 0) {
			if (value == null) {
				append(name, defaultValue);
			}
			value = defaultValue;
		}
		return value;
	}

	/**
	 * @param name
	 *            nazwa właściwości
	 * @param defaultValue
	 *            wartość domyślna w przypadku braku wartości lub jeśli jest nią
	 *            pusty ciąg znaków
	 * @return wartość właściwości jako int lub defaultValue jeśli brak
	 *         wartości/właściwości lub jeśli nastąpi błąd parsowania
	 */
	public int getInteger(String name, int defaultValue) {
		String value = getProperty(name);
		if (value == null || value.length() == 0) {
			return defaultValue;
		}
		try {
			return Integer.parseInt(value);
		} catch (NumberFormatException e) {
			return defaultValue;
		}
	}

	/**
	 * @param name
	 *            nazwa właściwości
	 * @param defaultValue
	 *            wartość domyślna w przypadku braku wartości lub jeśli jest nią
	 *            pusty ciąg znaków
	 * @return wartość właściwości jako int lub defaultValue jeśli brak
	 *         wartości/właściwości lub jeśli nastąpi błąd parsowania -1
	 */
	public int getInteger(String name, String defaultValue) {
		try {
			int liczba = Integer.parseInt(name);
			int value = getInteger(name, liczba);
			return value;
		} catch (NumberFormatException e) {
			return -1;
		}
	}

	/**
	 * @param name
	 *            nazwa właściwości
	 * @param defaultValue
	 *            wartość domyślna w przypadku braku wartości lub jeśli jest nią
	 *            pusty ciąg znaków
	 * @return wartość właściwości jako double lub defaultValue jeśli brak
	 *         wartości/właściwości lub jeśli nastąpi błąd parsowania
	 */
	public double getDouble(String name, double defaultValue) {
		String value = getProperty(name);
		if (value == null || value.length() == 0) {
			return defaultValue;
		}
		try {
			return Double.parseDouble(value);
		} catch (NumberFormatException e) {
			return defaultValue;
		}
	}

	/**
	 * @param name
	 *            nazwa właściwości
	 * @param defaultValue
	 *            wartość domyślna w przypadku braku wartości lub jeśli jest nią
	 *            pusty ciąg znaków
	 * @return wartość właściwości jako double lub defaultValue jeśli brak
	 *         wartości/właściwości lub jeśli nastąpi błąd parsowania -1
	 */
	public double getDouble(String name, String defaultValue) {
		try {
			double liczba = Double.parseDouble(name);
			return getDouble(name, liczba);
		} catch (NumberFormatException e) {
			return -1;
		}
	}

	/**
	 * @param name
	 *            nazwa właściwości
	 * @param defaultValue
	 *            domyślna wartość
	 * @return wartość właściwości jako zmienna logiczna
	 */
	public boolean getBoolean(String name, boolean defaultValue) {
		String value = getProperty(name);
		if (value == null || value.length() == 0) {
			return defaultValue;
		}
		return ((value != null) && value.toLowerCase().equals(TRUE));
	}

	/**
	 * @param name
	 *            nazwa właściwości
	 * @param defaultValue
	 *            domyślna wartość
	 * @return wartość właściwości jako zmienna logiczna
	 */
	public boolean getBoolean(String name, String defaultValue) {
		String value = getProperty(name);
		if (value == null || value.length() == 0) {
			return defaultValue.toLowerCase().equals(TRUE);
		}
		return ((value != null) && value.toLowerCase().equals(TRUE));
	}

	/**
	 * @param name
	 *            nazwa właściwości
	 * @return wartość właściwości jako zmienna logiczna
	 */
	public boolean getBoolean(String name) {
		String value = getProperty(name);
		return ((value != null) && value.toLowerCase().equals(TRUE));
	}

	/**
	 * Ładuje właściwości z pliku
	 */
	private synchronized void loadProperties() {
		boolean success = false;
		try {
			File file = new File(properFile);
			if (!file.exists()) {
				System.err
						.println("Nie znaleziono pliku właściwości: utworzenie nowego pliku");
				if (file.createNewFile()) {
					if (file.exists()) {
						System.err.println("Utworzono nowy plik:"
								+ file.getAbsolutePath() + "::"
								+ file.getName());
						System.err
								.println("Tworzę podstawowy zestaw właściwości");
						if (createDefaultValues()) {
							System.err
									.println("Utworzono podstawowy zestaw właściwości");
						}
					}
				}
			} else {
				System.err.println("Znaleziono plik właściwości w katalogu:"
						+ file.getAbsolutePath());
			}
			if (!file.canRead()) {
				throw new FileNotFoundException("Brak dostępu do pliku");
			}
			FileInputStream fis = new FileInputStream(file);
			proper = new Properties();
			proper.load(fis);
			fis.close();
			success = true;
		} catch (FileNotFoundException fnfe) {
			System.err.println("ERROR: " + fnfe);
			success = false;
		} catch (SecurityException se) {
			System.err.println("ERROR: " + se);
			success = false;
		} catch (IOException e) {
			System.err.println("ERROR: " + e);
			success = false;
		} finally {
			loaded = success;
		}
	}

	/**
	 * Tworzy podstawowy zestaw właściwości jeśli nie istnieje plik właścowiści
	 * 
	 * @return
	 */
	private boolean createDefaultValues() {
		try {
			File file = new File(properFile);
			BufferedWriter writer = new BufferedWriter(new FileWriter(file,
					true));
			append(writer, def + "type", DEFAULT_TYPE);
			append(writer, def + "host", DEFAULT_HOST);
			append(writer, def + "url", DEFAULT_URL);
			append(writer, def + "port", DEFAULT_PORT);
			append(writer, def + "driver", DEFAULT_DRIVER_NAME);
			writer.close();
			return true;
		} catch (IOException e) {
			System.err.println("ERROR: " + e);
			return false;
		}
	}

	/**
	 * Załącza właściwośc do buforra pliku
	 * 
	 * @param writer
	 * @param n
	 * @param v
	 * @return
	 * @throws IOException
	 */
	private boolean append(BufferedWriter writer, String n, String v)
			throws IOException {
		writer.write(n + "=" + v);
		writer.newLine();
		System.err.println("Dodano właściwość:" + n + "=" + v);
		return true;
	}

	/**
	 * @param n
	 *            załącza pojedyńcza opcję do pliku właścowości
	 * @param v
	 * @return
	 */
	private boolean append(String n, String v) {
		try {
			File file = new File(properFile);
			if (file.exists()) {
				BufferedWriter writer = new BufferedWriter(new FileWriter(file,
						true));
				writer.write(n + "=" + v);
				writer.newLine();
				writer.close();
				System.err.println("Dodano właściwość:" + n + "=" + v);
				return true;
			} else {
				if (file.createNewFile()) {
					if (file.exists()) {
						System.err.println("Utworzono nowy plik:"
								+ file.getAbsolutePath() + "::"
								+ file.getName());
					}
				}
			}
			return false;
		} catch (IOException e) {
			System.err.println("ERROR: " + e);
			return false;
		}
	}

	/**
	 * @return true jeśli plik został poprawnie zaladowany
	 */
	private boolean isLoaded() {
		return (instance != null) ? loaded : false;
	}

	/**
	 * @return domyślna nazwa typu bazy
	 */
	public String getDbType() {
		return getProperty(def + "type", DEFAULT_TYPE);
	}

	/**
	 * @return domyślna nazwa hosta
	 */
	public String getHostName() {
		return getProperty(def + "host", DEFAULT_HOST);
	}

	/**
	 * @return domyślna nazwa hosta
	 */
	public String getURL() {
		return getProperty(def + "url", DEFAULT_URL);
	}

	/**
	 * @return URL połączenia "jdbc:oracle:thin:@"+host+":"+port+":"+dbn
	 *         "jdbc:postgresql://"+host+":"+port+"/"+dbn
	 */
	public String getURL(String host, String port, String dbn) {
		if (host != null && host.length() > 0 && port != null
				&& port.length() > 0 && dbn != null && dbn.length() > 0) {
			return new String("jdbc:oracle:thin:@" + host + ":" + port + ":"
					+ dbn);
			// return new String("jdbc:postgresql://"+host+":"+port+"/"+dbn);
		}
		return getURL();
	}
	/**
	 * @return URL połączenia
	 */
	public String getURL(DbUser u, String driver) {
		if (u != null) {
			String url = urls.get(driver);
			if(url!=null){
				String out = url.replace("_host_", u.getHostName()).
								 replace("_port_", String.valueOf(u.getPortNumber())).
								 replace("_db_", u.getDbName()).
								 replace("_user_", u.getName()).
								 replace("_pass_", u.getPass());
				return out;
			}
			return getURL(u);
		}
		return getURL();
	}
	/**
	 * @return URL połączenia
	 */
	public String getURL(DbUser u) {
		if (u != null) {
			return getURL(u.getHostName(), String.valueOf(u.getPortNumber()), u
					.getDbName());
		}
		return getURL();
	}

	/**
	 * @return domyślny numer portu
	 */
	public String getPort() {
		return getProperty(def + "port", DEFAULT_PORT);
	}

	/**
	 * @return domyślna nazwa starownika
	 */
	public String getDrivertName() {
		return getProperty(def + "driver", DEFAULT_DRIVER_NAME);
	}

	/**
	 * @return domyślny wartość automatycznie zamykanego połączenia
	 */
	public boolean defAutoClose() {
		return getBoolean(def + "connection.autoClose", AUTO_CLOSE);
	}

	public boolean showAllSchemas() {
		return getBoolean(def + "show.all.schemas", showAllSchemas);
	}

	public boolean alwaysCheckPrives() {
		return getProperty(def + "cheking.privs", checkPrivs).equalsIgnoreCase(
				checkPrivs);
	}
	private static Map<String, String> urls = new HashMap<String, String>(){{
		put("com.mysql.jdbc.Driver", "jdbc:mysql://_host_:_port_/_db_");//port:3306
		put("org.postgresql.Driver", "jdbc:postgresql://_host_:_port_/_db_");//port:5432
		put("oracle.jdbc.driver.OracleDriver", "jdbc:oracle:thin:@_host_:_port_:_db_");//port:1521
		put("com.microsoft.jdbc.sqlserver.SQLServerDriver", "jdbc:microsoft:sqlserver://_host_:_port_;databasename=_db_;user=_user_;password=_pass_");//port:1433
		put("org.apache.derby.jdbc.EmbeddedDriver", "jdbc:derby:_db_");
	}};
	/**
	 * Zmienne nazw właściwości
	 */
	private static final String def = "default.";
}
